<!DOCTYPE HTML>
<html>
<head><meta name="generator" content="Hexo 3.9.0">
  <meta charset="utf-8">
  
  <title>网络编程模型 › 但行好事，莫问前程</title>
  <meta name="author" content="fengbo">
  
  <meta name="description" content="不想当架构师的程序员不是好厨师 ，不认为PHP是世界上最好的编程语言的Python开发者不是一个好的Javaer。">
  
  
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

  <meta property="og:title" content="网络编程模型">
  <meta property="og:site_name" content="但行好事，莫问前程">

  
    <meta property="og:image" content="undefined">
  

  <link href="/favicon.png" rel="icon">
  <link rel="alternate" href="/atom.xml" title="但行好事，莫问前程" type="application/atom+xml">
  <link rel="stylesheet" href="/css/style.css" media="screen" type="text/css">
  <!--[if lt IE 9]><script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
  

</head>
</html>

<body>
  <header id="header"><div class="meta inner">
  <h1><a href="/">但行好事，莫问前程</a></h1>
  <h2><a href="/">CV工程师，了解面向Google编程的基本原理，有三年使用Google经验，在通往程序员的道路上艰难的爬行着。</a></h2>
  <nav id="main-nav">
    <ul>
      
      <li><a href="/">Home</a></li>
      
      <li><a href="/archives">Archives</a></li>
      
      <li><a href="/atom.xml">RSS</a></li>
      
    </ul>
    <div class="clearfix"></div>
  </nav>
</div>
<div class="clearfix"></div>
</header>
  <div id="content" class="inner">
    <div id="main-col" class="alignleft"><div id="wrapper"><article class="post">
  <div class="post-content">
    <header>
      
  
    <h1 class="title">网络编程模型</h1>
  

      
        <time datetime="2019-10-10T02:22:43.000Z">2019-10-10</time>
      
    </header>
    <div class="entry">
      
        <h3 id="线程驱动和事件驱动"><a href="#线程驱动和事件驱动" class="headerlink" title="线程驱动和事件驱动"></a>线程驱动和事件驱动</h3><ul>
<li>线程驱动：当收到一个请求的时候，将会为该请求开一个新的线程来处理请求。一般存在一个线程池，线程池中有空闲的线程，会从线程池中拿取线程来进行处理，如果线程池中没有空闲的线程，新来的请求将会进入队列排队，直到线程池中空闲线程。</li>
<li>事件驱动：当进来一个新的请求的时，请求将会被压入队列中，然后通过一个循环来检测队列中的事件状态变化，如果检测到有状态变化的事件，那么就执行该事件对应的处理代码，一般都是回调函数。<b>对于事件驱动编程来说，如果某个时间的回调函数是计算密集型，或者是阻塞I/O,那么这个回调函数将会阻塞后面所有事件回调函数的执行。</b><a id="more"></a>
</li>
</ul>
<h2 id="五种I-O模型"><a href="#五种I-O模型" class="headerlink" title="五种I/O模型"></a>五种I/O模型</h2><p>对于Unix的一次I/O访问（以read为例），数据会先被拷贝到操作系统的缓冲区中，然后才会从操作系统的缓冲区拷贝到应用程序的地址空间。所以说，当一个read操作发生的时候，会经历两个阶段：</p>
<ol>
<li>等待数据准备 (Waiting for the data to be ready)。</li>
<li>将数据从内核中拷贝到进程中（Copying the data from the kernel to the process）。</li>
</ol>
<h3 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h3><ul>
<li>文件描述符：是一个用于表述指向文件的引用的抽象化概念。文件描述符在形式上是一个非负整数。实际上，它是一个索引值，指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或创建一个新文件时，内核向进程返回一个文件描述符。在程序设计中，一些涉及底层的程序编写往往会围绕着文件描述符展开。</li>
<li>缓存I/O：又称标准I/O，大多数文件系统中默认I/O都是缓存I/O。在Linux的缓存I/O机制中，操作系统会将I/O的数据缓存在文件系统的页缓存中，也就是说，数据会先被拷贝到操作系统内核的缓冲区中，然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作，这些数据拷贝操作所带来的CPU以及内存开销是非常大的。</li>
</ul>
<h3 id="阻塞式I-O模型"><a href="#阻塞式I-O模型" class="headerlink" title="阻塞式I/O模型"></a>阻塞式I/O模型</h3><p>在Linux中，默认情况下所有的Socket都是阻塞的。当用户进程调用了recvfrom这个系统调用，内核就开始了IO的第一个阶段：准备数据。对于网络IO来说，很多时候数据一开始还没有到达（比如，还没有收到一个完整的UDP包），这个时候内核就要等待足够的数据到来。而在用户进程这边，整个进程就会被阻塞（是进程自己选择的阻塞）。当内核的数据准备好了，用户进程才解除阻塞的状态并将数据从内核拷贝到用户内存。</p>
<blockquote>
<p><b>阻塞I/O的特点是在I/O执行的两个阶段都被阻塞了。</b></p>
</blockquote>
<h3 id="非阻塞式I-O模型"><a href="#非阻塞式I-O模型" class="headerlink" title="非阻塞式I/O模型"></a>非阻塞式I/O模型</h3><p>Linux中也可以设置Socket使其变成非阻塞。即当用户发出read操作时，如果内核中数据还没有准备好，那么它并不会阻塞用户进程，而是立即返回一个错误。从用户进程的角度讲，它发起一个read操作后，并不需要等待，而是马上就得到了一个结果。用户进程判断结果是一个error时，它就知道数据还没有准备好，于是它可以再次发送read操作。一旦内核中的数据准备好了，并且又再次收到了用户进程的系统调用，那么用户进程就可以就将数据拷贝到了用户内存。</p>
<blockquote>
<p><b>非阻塞I/O虽然在第一阶段不会阻塞，但是用户进程需要不断的主动询问内核数据是否准备好了。</b></p>
</blockquote>
<h3 id="I-O复用模型-select、poll、epoll"><a href="#I-O复用模型-select、poll、epoll" class="headerlink" title="I/O复用模型 - select、poll、epoll"></a>I/O复用模型 - select、poll、epoll</h3><p>应用进程调用select函数并阻塞在该系统调用上，而不是阻塞在真正的I/O系统调用上。然后将Socket注册到该函数上，该函数会不断的轮询所负责的所有Socket，当某个Socket有数据到达了，就通知用户进程。然后用户进程调用recvfrom把所读数据报复制到应用进程缓冲区。将数据从内核拷贝到用户进程。</p>
<blockquote>
<p><b>I/O复用优势在于单个进程可以处理多个网络连接，但在单个网络连接上并不显得有什么优势，事实上由于使用select需要两个而不是单个系统调用，I/O复用还稍有劣势。</b></p>
</blockquote>
<h3 id="信号驱动式I-O模型"><a href="#信号驱动式I-O模型" class="headerlink" title="信号驱动式I/O模型"></a>信号驱动式I/O模型</h3><p>也可以用信号，让内核在描述符就绪时发送SIGIO信号通知用户进程。首先开启套接字的信号驱动式I/O功能并通过sigaction系统调用安装一个信号处理函数。该系统调用将立即返回，用户进程继续工作，也就是说它没有被阻塞。当数据报准备好读取时，内核就为该进程产生一个SIGIO信号。随后既可以在信号处理函数中调用recvfrom读取数据报，并通知主循环数据已准备好待处理，也可以立即通知主循环，让它读取数据报。</p>
<blockquote>
<p><b>模型的优势在于等待数据报到达期间进程不被阻塞。主循环可以继续执行，只要等待来自信号处理函数的通知：既可以是数据已准备好被处理，也可以是数据报已准备好被读取。</b></p>
</blockquote>
<h3 id="异步I-O模型"><a href="#异步I-O模型" class="headerlink" title="异步I/O模型"></a>异步I/O模型</h3><p>告知内核启动某个操作，并让内核在整个操作（包括将数据从内核复制到我们自己的缓冲区）完成后通知我们。这种模型与信号驱动模型的主要区别在于：信号驱动式I/O是由内核通知我们何时可以启动一个I/O操作，而异步I/O模型是由内核通知我们I/O操作何时完成。</p>
<blockquote>
<p><b>异步I/O的特点是在I/O的两个阶段都不会阻塞。</b></p>
</blockquote>
<h3 id="一般来说，服务端IO主要有两种情况：来着网络的IO和对文件（设备）的IO。Windows的AIO模型能很好的适用于这两种情况。而Linux针对前者提供了epoll模型，针对后者提供了AIO模型。"><a href="#一般来说，服务端IO主要有两种情况：来着网络的IO和对文件（设备）的IO。Windows的AIO模型能很好的适用于这两种情况。而Linux针对前者提供了epoll模型，针对后者提供了AIO模型。" class="headerlink" title="一般来说，服务端IO主要有两种情况：来着网络的IO和对文件（设备）的IO。Windows的AIO模型能很好的适用于这两种情况。而Linux针对前者提供了epoll模型，针对后者提供了AIO模型。"></a>一般来说，服务端IO主要有两种情况：来着网络的IO和对文件（设备）的IO。Windows的AIO模型能很好的适用于这两种情况。而Linux针对前者提供了epoll模型，针对后者提供了AIO模型。</h3><h2 id="IO复用模型"><a href="#IO复用模型" class="headerlink" title="IO复用模型"></a><a href="https://segmentfault.com/a/1190000003063859?utm_source=Weibo&amp;utm_medium=shareLink&amp;utm_campaign=socialShare&amp;from=timeline&amp;isappinstalled=0" target="_blank" rel="noopener">IO复用模型</a></h2><h3 id="select、poll、epoll都是IO多路复用的机制的实现。I-O多路复用就是通过一种机制，一个进程可以监视多个描述符，一旦某个描述符就绪（一般是读就绪或者写就绪），能够通知程序进行相应的读写操作。但select，poll，epoll本质上都是同步I-O，因为他们都需要在读写事件就绪后自己负责进行读写，也就是说这个读写过程是阻塞的，而异步I-O则无需自己负责进行读写，异步I-O的实现会负责把数据从内核拷贝到用户空间。"><a href="#select、poll、epoll都是IO多路复用的机制的实现。I-O多路复用就是通过一种机制，一个进程可以监视多个描述符，一旦某个描述符就绪（一般是读就绪或者写就绪），能够通知程序进行相应的读写操作。但select，poll，epoll本质上都是同步I-O，因为他们都需要在读写事件就绪后自己负责进行读写，也就是说这个读写过程是阻塞的，而异步I-O则无需自己负责进行读写，异步I-O的实现会负责把数据从内核拷贝到用户空间。" class="headerlink" title="select、poll、epoll都是IO多路复用的机制的实现。I/O多路复用就是通过一种机制，一个进程可以监视多个描述符，一旦某个描述符就绪（一般是读就绪或者写就绪），能够通知程序进行相应的读写操作。但select，poll，epoll本质上都是同步I/O，因为他们都需要在读写事件就绪后自己负责进行读写，也就是说这个读写过程是阻塞的，而异步I/O则无需自己负责进行读写，异步I/O的实现会负责把数据从内核拷贝到用户空间。"></a>select、poll、epoll都是IO多路复用的机制的实现。I/O多路复用就是通过一种机制，一个进程可以监视多个描述符，一旦某个描述符就绪（一般是读就绪或者写就绪），能够通知程序进行相应的读写操作。但select，poll，epoll本质上都是同步I/O，因为他们都需要在读写事件就绪后自己负责进行读写，也就是说这个读写过程是阻塞的，而异步I/O则无需自己负责进行读写，异步I/O的实现会负责把数据从内核拷贝到用户空间。</h3><h3 id="select模式"><a href="#select模式" class="headerlink" title="select模式"></a>select模式</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">select</span><span class="params">(<span class="keyword">int</span> n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)</span></span>;</span><br></pre></td></tr></table></figure>
<ol>
<li>当用户进程调用了select函数，那么整个进程会被阻塞，同时，内核被委托会监视所有select负责的socket，当任何一个socket中的数据准备好了，select就会返回。这个时候用户进程再调用read操作，将数据从kernel拷贝到用户进程。</li>
<li>select函数监视的文件描述符分3类，分别是writefds、readfds、和exceptfds。调用后select函数会阻塞，直到有描述符就绪（有数据 可读、可写、或者有except），或者超时（timeout指定等待时间），函数返回。select函数只返回就绪的描述符的数量而不能具体到那个描述符，所以需要遍历fd_set，来找到真正就绪的描述符。</li>
</ol>
<h3 id="select模式的缺点"><a href="#select模式的缺点" class="headerlink" title="select模式的缺点"></a>select模式的缺点</h3><ol>
<li>每次调用select，都需要把fd集合从用户态拷贝到内核态，同时每次调用select都需要在内核遍历传递进来的所有fd，增加了系统开销。</li>
<li>内核和用户进程都需要遍历fd_set来获取就绪的描述符，所以当文件描述符数量过大后性能会下降。</li>
<li>select支持的文件描述符数量太小了，默认是1024。</li>
</ol>
<h3 id="poll和select的区别"><a href="#poll和select的区别" class="headerlink" title="poll和select的区别"></a>poll和select的区别</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">poll</span><span class="params">(struct pollfd *fds, <span class="keyword">unsigned</span> <span class="keyword">int</span> nfds, <span class="keyword">int</span> timeout)</span></span>;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">pollfd</span> &#123;</span></span><br><span class="line">    <span class="keyword">int</span> fd; <span class="comment">/* 文件描述发 */</span></span><br><span class="line">    short events; <span class="comment">/* 等待的事件 */</span></span><br><span class="line">    short revents; <span class="comment">/* 实际发生的事件 */</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>poll的实现和select非常相似，只是描述fd集合的方式不同，poll使用pollfd结构体而不是select的fd_set数组结构。同时，pollfd并没有最大数量限制，但是数量过大后性能也是会下降，因为poll函数返回后，也需要需要轮询pollfd来获取就绪的描述符。</p>
<h3 id="epoll函数介绍"><a href="#epoll函数介绍" class="headerlink" title="epoll函数介绍"></a><a href="https://blog.csdn.net/yusiguyuan/article/details/41287549" target="_blank" rel="noopener">epoll函数介绍</a></h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">int epoll_create(int size)；</span><br><span class="line">int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)；</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">epoll_wait</span><span class="params">(<span class="keyword">int</span> epfd, struct epoll_event * events, <span class="keyword">int</span> maxevents, <span class="keyword">int</span> timeout)</span></span>;</span><br></pre></td></tr></table></figure>
<ol>
<li>epoll_create函数：创建一个epoll_event树的根节点，size用来告诉内核这个epoll可能监听的文件描述符数目，该参数并不是限制了epoll所能监听的描述符最大个数，只是对内核初始分配内部数据结构的一个建议。当创建好epoll句柄后，它就会占用一个fd值，在linux下可以通过查看/proc/进程id/fd/查看这个fd，所以在使用完epoll后，必须调用close()关闭，否则可能导致fd被耗尽。</li>
<li><p>epoll_ctl函数：对指定描述符fd执行op操作。</p>
<ul>
<li>epfd：是epoll_create函数的返回值，即句柄数的根节点。</li>
<li>op：表示op操作，用三个宏来表示，添加EPOLL_CTL_ADD、删除EPOLL_CTL_DEL、修改EPOLL_CTL_MOD。分别添加、删除和修改对fd的监听事件。</li>
<li>fd：是需要监听的fd（文件描述符）。</li>
<li>epoll_event：是告诉内核需要监听什么事，struct epoll_event结构如下：<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">epoll_event</span> &#123;</span></span><br><span class="line">  <span class="keyword">__uint32_t</span> events;  <span class="comment">/* Epoll events */</span></span><br><span class="line">  <span class="keyword">epoll_data_t</span> data;  <span class="comment">/* User data variable */</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">typedef</span> <span class="keyword">union</span> <span class="keyword">epoll_data_t</span> &#123;</span><br><span class="line">    <span class="keyword">void</span> *ptr;</span><br><span class="line">    <span class="keyword">int</span> fd;</span><br><span class="line">    <span class="keyword">uint32_t</span> u32;</span><br><span class="line">    <span class="keyword">uint64_t</span> u64;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// events可以是以下几个宏的集合：</span></span><br><span class="line">EPOLLIN ：表示对应的文件描述符可以读（包括对端SOCKET正常关闭）；</span><br><span class="line">EPOLLOUT：表示对应的文件描述符可以写；</span><br><span class="line">EPOLLPRI：表示对应的文件描述符有紧急的数据可读（这里应该表示有带外数据到来）；</span><br><span class="line">EPOLLERR：表示对应的文件描述符发生错误；</span><br><span class="line">EPOLLHUP：表示对应的文件描述符被挂断；</span><br><span class="line">EPOLLET： 将EPOLL设为边缘触发(Edge Triggered)模式，这是相对于水平触发(Level Triggered)来说的。</span><br><span class="line">EPOLLONESHOT：只监听一次事件，当监听完这次事件之后，如果还需要继续监听这个socket的话，需要再次把这个socket加入到EPOLL队列里</span><br></pre></td></tr></table></figure>
</li>
</ul>
</li>
<li><p>epoll_wait函数：等待epfd上的io事件，最多返回maxevents个事件。参数events用来从内核得到事件的集合，maxevents告之内核这个events有多大，这个maxevents的值不能大于创建epoll_create()时的size，参数timeout是超时时间（毫秒，0会立即返回，-1将不确定，也有说法说是永久阻塞）。该函数返回需要处理的事件数目，如返回0表示已超时。</p>
</li>
</ol>
<h3 id="水平触发和边缘触发"><a href="#水平触发和边缘触发" class="headerlink" title="水平触发和边缘触发"></a>水平触发和边缘触发</h3><p>一个管道收到了1kb的数据，epoll会立即返回，此时读了512字节数据，然后再次调用epoll。这时如果是水平触发的，epoll会立即返回，因为有数据准备好了。如果是边缘触发的不会立即返回，因为此时虽然有数据可读但是已经触发了一次通知，在这次通知到现在还没有新的数据到来，直到有新的数据到来epoll才会返回，此时老的数据和新的数据都可以读取到（当然是需要这次你尽可能的多读取）。</p>
<h2 id="零拷贝技术"><a href="#零拷贝技术" class="headerlink" title="零拷贝技术"></a>零拷贝技术</h2><p>传统的Linux操作系统的标准I/O接口是基于数据拷贝操作的，即I/O操作会导致数据在操作系统内核地址空间的缓冲区和应用程序地址空间定义的缓冲区之间进行传输。这样做最大的好处是可以减少磁盘I/O的操作，因为如果所请求的数据已经存放在操作系统的高速缓冲存储器中，那么就不需要再进行实际的物理磁盘I/O操作。但是数据传输过程中的数据拷贝操作却导致了极大的CPU开销，限制了操作系统有效进行数据传输操作的能力。而零拷贝可以避免在用户态(User-space)与内核态(Kernel-space)之间来回拷贝数据。</p>
<h3 id="缓冲I-O进行数据传输的流程"><a href="#缓冲I-O进行数据传输的流程" class="headerlink" title="缓冲I/O进行数据传输的流程"></a>缓冲I/O进行数据传输的流程</h3><ol>
<li>当应用程序需要访问某块数据的时候，操作系统内核会先检查这块数据是不是因为前一次对相同文件的访问而已经被存放在操作系统内核地址空间的缓冲区内，如果在内核缓冲区中找不到这块数据，Linux操作系统内核会先将这块数据从磁盘读出来放到操作系统内核的缓冲区里去。如果这个数据读取操作是由DMA完成的，那么在DMA进行数据读取的这一过程中，CPU只是需要进行缓冲区管理，以及创建和处理DMA，除此之外，CPU不需要再做更多的事情。<ul>
<li>DMA(Direct Memory Access)—直接存储器存取，是单片机的一个外设，它的主要功能是用来搬数据，但是不需要占用CPU，即在传输数据的时候，CPU可以干其他的事情，好像是多线程一样。</li>
</ul>
</li>
<li>DMA执行完数据读取操作之后，会通知操作系统做进一步的处理。Linux操作系统会根据read()系统调用指定的应用程序地址空间的地址，把这块数据存放到请求这块数据的应用程序的地址空间中去。</li>
<li>在接下来的write()系统调用过程中，操作系统需要将数据再一次从用户应用程序地址空间的缓冲区拷贝到与网络堆栈相关的内核缓冲区中去，这个过程也是需要占用CPU的。</li>
<li>数据拷贝操作结束以后，数据会被打包，然后发送到网络接口卡上去。在数据传输的过程中，应用程序可以先返回进而执行其他的操作。</li>
</ol>
<h3 id="Linux中的零拷贝技术"><a href="#Linux中的零拷贝技术" class="headerlink" title="Linux中的零拷贝技术"></a>Linux中的零拷贝技术</h3><ul>
<li>直接I/O：对于这种数据传输方式来说，应用程序可以直接访问硬件存储，操作系统内核只是辅助数据传输：这类零拷贝技术针对的是<b>操作系统内核并不需要对数据进行直接处理</b>的情况，数据可以在应用程序地址空间的缓冲区和磁盘之间直接进行传输，完全不需要Linux操作系统内核提供的页缓存的支持。</li>
<li><b>应用程序在数据进行传输的过程中不需要对数据进行访问</b>时将数据从Linux的页缓存拷贝到用户进程的缓冲区中就可以完全避免，传输的数据在页缓存中就可以得到处理。Linux中提供类似的系统调用主要有mmap()、sendfile()以及splice()。</li>
<li>如果操作系统内核和应用程序都需要堆数据进行处理时需要延续了传统的通信方式，但是对页缓存和用户进程的缓冲区之间的传输过程进行优化。在Linux中，该方法主要利用了写时复制技术。</li>
</ul>
<h3 id="Netty中的零拷贝"><a href="#Netty中的零拷贝" class="headerlink" title="Netty中的零拷贝"></a><a href="https://segmentfault.com/a/1190000007560884" target="_blank" rel="noopener">Netty中的零拷贝</a></h3><p>Netty的零拷贝完全是在用户态(Java层面)的，它的零拷贝的更多的是偏向于优化数据操作这样的概念。</p>
<ul>
<li>Netty提供了CompositeByteBuf类，它可以将多个ByteBuf合并为一个逻辑上的ByteBuf，避免了各个ByteBuf之间的拷贝。</li>
<li>通过wrap操作，我们可以将byte[]数组、ByteBuf、ByteBuffer等包装成一个Netty ByteBuf对象，进而避免了拷贝操作。</li>
<li>ByteBuf支持slice操作，因此可以将ByteBuf分解为多个共享同一个存储区域的ByteBuf，避免了内存的拷贝。</li>
<li>通过FileRegion包装的FileChannel.tranferTo实现文件传输，可以直接将文件缓冲区的数据发送到目标Channel，避免了传统通过循环write方式导致的内存拷贝问题。</li>
</ul>
<h3 id="sendfile-函数"><a href="#sendfile-函数" class="headerlink" title="sendfile()函数"></a>sendfile()函数</h3><ol>
<li>sendfile()系统调用利用DMA引擎将文件中的数据拷贝到操作系统内核缓冲区中后数据被拷贝到与socket相关的内核缓冲区中去。然后网卡的DMA引擎将数据从内核socket缓冲区中拷贝到协议引擎中去。该操作在进行数据传输仍然还需要一次多余的数据拷贝操作。</li>
<li>在Linux2.4中将文件中的数据拷贝到操作系统内核缓冲区中后直接将带有文件位置和长度信息的缓冲区描述符添加到socket缓冲区中去，此过程不需要将数据从操作系统内核缓冲区拷贝到socket缓冲区中，网卡的DMA引擎会将数据直接从内核缓冲区拷贝到协议引擎中去。</li>
</ol>
<p>相关参考：<a href="https://www.ibm.com/developerworks/cn/linux/l-cn-zerocopy1/index.html" target="_blank" rel="noopener">Linux中的零拷贝技术1</a> | <a href="https://www.ibm.com/developerworks/cn/linux/l-cn-zerocopy2/index.html" target="_blank" rel="noopener">Linux中的零拷贝技术2</a></p>

      
    </div>
      
      <footer>
        
        
  
  <div class="tags">
    <a href="/tags/计算机基础/">计算机基础</a>
  </div>

        
  <div class="addthis addthis_toolbox addthis_default_style">
    
      <a class="addthis_button_facebook_like" fb:like:layout="button_count"></a>
    
    
      <a class="addthis_button_tweet"></a>
    
    
      <a class="addthis_button_google_plusone" g:plusone:size="medium"></a>
    
    
      <a class="addthis_button_pinterest_pinit" pi:pinit:layout="horizontal"></a>
    
    <a class="addthis_counter addthis_pill_style"></a>
  </div>
  <script type="text/javascript" src="//s7.addthis.com/js/300/addthis_widget.js"></script>

      
      <div class="clearfix"></div>
      </footer>
  </div>
</article>

<!-- 来必力City版安装代码 -->
<!-- <div id="lv-container" data-id="city" data-uid="MTAyMC8yOTQ5MS82MDU5">
<script type="text/javascript">
   (function(d, s) {
       var j, e = d.getElementsByTagName(s)[0];

       if (typeof LivereTower === 'function') { return; }

       j = d.createElement(s);
       j.src = 'https://cdn-city.livere.com/js/embed.dist.js';
       j.async = true;

       e.parentNode.insertBefore(j, e);
   })(document, 'script');
</script>
<noscript>为正常使用来必力评论功能请激活JavaScript</noscript>
</div> -->
<!-- City版安装代码已完成 -->

</div></div>
    <aside id="sidebar" class="alignright">
  
<div class="widget tagcloud">
  <h3 class="title">文章分类</h3>
  <div class="entry">
    <a href="/tags/JavaWeb/" style="font-size: 17.14px;">JavaWeb</a> <a href="/tags/Java基础/" style="font-size: 15.71px;">Java基础</a> <a href="/tags/Java高级特性/" style="font-size: 15.71px;">Java高级特性</a> <a href="/tags/Python/" style="font-size: 10px;">Python</a> <a href="/tags/Spring基础/" style="font-size: 14.29px;">Spring基础</a> <a href="/tags/WebRTC/" style="font-size: 10px;">WebRTC</a> <a href="/tags/其它/" style="font-size: 20px;">其它</a> <a href="/tags/分布式/" style="font-size: 11.43px;">分布式</a> <a href="/tags/硬件/" style="font-size: 12.86px;">硬件</a> <a href="/tags/计算机基础/" style="font-size: 18.57px;">计算机基础</a> <a href="/tags/设计模式/" style="font-size: 17.14px;">设计模式</a>
  </div>
</div>



    <div class="widget tagcloud">
    <h3 class="title">电子书</h3>
    <ul class="entry">
      <li class='link'><a href='http://www.duokan.com/' target="_blank">多看阅读</a>&nbsp;&nbsp;<a href='https://read.douban.com/' target="_blank">豆瓣阅读</a>&nbsp;&nbsp;<a href='http://e.dangdang.com/' target="_blank">当当阅读</a></li>
      <li class='link'><a href='http://www.ituring.com.cn/' target="_blank">图灵社区</a>&nbsp;&nbsp;<a href='https://www.epubit.com/' target="_blank">异步社区</a>&nbsp;&nbsp;<a href='https://www.geekbang.org/' target="_blank">极客空间</a></li>
    </ul>
  </div>


  
  <div class="widget tag">
    <h3 class="title">友情链接</h3>
      <ul class="entry">
        
          <li class='link'><a href='http://www.cnblogs.com/jietang/' target="_blank">唐洁的博客</a></li>
        
          <li class='link'><a href='http://rednaxelafx.iteye.com/' target="_blank">R大的博客</a></li>
        
          <li class='link'><a href='http://www.hollischuang.com/' target="_blank">阿里大神的博客</a></li>
        
          <li class='link'><a href='http://blog.csdn.net/IT_faquir/' target="_blank">IT_faquir的专栏</a></li>
        
      </ul>
  </div>


  
  <div class="widget tag">
    <h3 class="title">思维导图</h3>
      <ul class="entry">
        
          <li class='link'><a href='https://fengbo4213.github.io/img/开源协议.png' target="_blank">开源协议简介</a></li>
        
          <li class='link'><a href='https://www.processon.com/diagraming/5b0cf757e4b009aef58d4b9d' target="_blank">我的知识体系</a></li>
        
          <li class='link'><a href='https://www.processon.com/view/link/5a2a00e5e4b015e677290b4f' target="_blank">简单的网站架构</a></li>
        
      </ul>
  </div>


  
  <div class="widget tag">
    <h3 class="title">在线手册</h3>
      <ul class="entry">
        
          <li class='link'><a href='http://v3.bootcss.com/' target="_blank">BootStrap官方手册</a></li>
        
          <li class='link'><a href='http://www.iconfont.cn/http://element.eleme.io/#/zh-CN/' target="_blank">阿里巴巴矢量图标库</a></li>
        
      </ul>
  </div>


  
<div class="widget tag">
  <h3 class="title">最新文章</h3>
  <ul class="entry">
    
      <li>
        <a href="/2123/08/30/基础之MySQL原理/">MySQL原理</a>
      </li>
    
      <li>
        <a href="/2024/01/01/其它之ClickHouse索引/">ClickHouse索引</a>
      </li>
    
      <li>
        <a href="/2023/12/17/其它之MySQL线程池/">MySQL线程池</a>
      </li>
    
      <li>
        <a href="/2023/12/16/其它之InnoDB性能优化基础/">InnoDB性能优化基础</a>
      </li>
    
      <li>
        <a href="/2023/12/03/其它之Java8之后的版本新特性/">Java8之后的版本新特性</a>
      </li>
    
  </ul>
</div>

</aside>
    <div class="clearfix"></div>
  </div>
  <footer id="footer" class="inner"><div class="alignleft">
  
  &copy; 2024 fengbo
  
</div>
<div class="clearfix"></div></footer>
  <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>



</body>
</html>

